<?php
namespace Leaps\Events;
use Leaps\Base\Container;
class Dispatcher
{

    /**
     * IoC容器实例
     *
     * @var \Leaps\Container\Container
     */
    protected $container;

    /**
     * 已注册的事件侦听器
     *
     * @var array
     */
    protected $listeners = array ();

    /**
     * 通配符的听众
     *
     * @var array
     */
    protected $wildcards = array ();

    /**
     * 排序后的事件监听器
     *
     * @var array
     */
    protected $sorted = array ();

    /**
     * 创建一个新的事件调度程序实例。
     *
     * @param \Leaps\Container\Container $container
     * @return void
     */
    public function __construct(Container $container = null)
    {
        $this->container = $container;
    }

    /**
     * 从调度器注册一个事件监听器
     *
     * @param string $event
     * @param mixed $listener
     * @param int $priority
     * @return void
     */
    public function listen($event, $listener, $priority = 0)
    {
        if ( $this->contains ( $event, '*' ) ) {
            return $this->setupWildcardListen ( $event, $listener, $priority = 0 );
        }
        $this->listeners [$event] [$priority] [] = $this->makeListener ( $listener );
        unset ( $this->sorted [$event] );
    }

    /**
     * 设置一个通配符侦听器回调
     *
     * @param string $event
     * @param mixed $listener
     * @param int $priority
     * @return void
     */
    protected function setupWildcardListen($event, $listener, $priority)
    {
        $this->wildcards [$event] [] = $listener;
    }

    /**
     * 判断事件监听器是否存在
     *
     * @param string $eventName
     * @return bool
     */
    public function hasListeners($eventName)
    {
        return isset ( $this->listeners [$eventName] );
    }

    /**
     * 注册一个事件队列
     *
     * @param string $event
     * @param array $payload
     * @return void
     */
    public function queue($event, $payload = array())
    {
        $me = $this;
        $this->listen ( $event . '_queue', function () use($me, $event, $payload)
        {
            $me->fire ( $event, $payload );
        } );
    }

    /**
     * 注册一个事件订阅者
     *
     * @param string $subscriber
     * @return void
     */
    public function subscribe($subscriber)
    {
        $subscriber = $this->resolveSubscriber ( $subscriber );
        $subscriber->subscribe ( $this );
    }

    /**
     * 执行用户实例
     *
     * @param mixed $subscriber
     * @return mixed
     */
    protected function resolveSubscriber($subscriber)
    {
        if ( is_string ( $subscriber ) ) {
            return $this->container->make ( $subscriber );
        }
        return $subscriber;
    }

    /**
     * 触发一个事件,直到第一个非空响应返回。
     *
     * @param string $event
     * @param array $payload
     * @return mixed
     */
    public function until($event, $payload = array())
    {
        return $this->fire ( $event, $payload, true );
    }

    /**
     * 清空一组队列事件
     *
     * @param string $event
     * @return void
     */
    public function flush($event)
    {
        $this->fire ( $event . '_queue' );
    }

    /**
     * Fire an event and call the listeners.
     *
     * @param string $event
     * @param mixed $payload
     * @param boolean $halt
     * @return void
     */
    public function fire($event, $payload = array(), $halt = false)
    {
        $responses = array ();
        if ( ! is_array ( $payload ) ) $payload = array (
                $payload
        );
        $payload [] = $event;
        foreach ( $this->getListeners ( $event ) as $listener ) {
            $response = call_user_func_array ( $listener, $payload );
            if ( ! is_null ( $response ) and $halt ) {
                return $response;
            }
            if ( $response === false ) break;
            $responses [] = $response;
        }
        return $halt ? null : $responses;
    }

    /**
     * 从事件名称获取所有听众
     *
     * @param string $eventName
     * @return array
     */
    public function getListeners($eventName)
    {
        $wildcards = $this->getWildcardListeners ( $eventName );
        if ( ! isset ( $this->sorted [$eventName] ) ) {
            $this->sortListeners ( $eventName );
        }
        return array_merge ( $this->sorted [$eventName], $wildcards );
    }

    /**
     * Get the wildcard listeners for the event.
     *
     * @param string $eventName
     * @return array
     */
    protected function getWildcardListeners($eventName)
    {
        $wildcards = array ();
        foreach ( $this->wildcards as $key => $listeners ) {
            if ( $this->is ( $key, $eventName ) ) $wildcards = array_merge ( $wildcards, $listeners );
        }
        return $wildcards;
    }

    /**
     * 按优先级排序一个事件监听器
     *
     * @param string $eventName
     * @return array
     */
    protected function sortListeners($eventName)
    {
        $this->sorted [$eventName] = array ();
        if ( isset ( $this->listeners [$eventName] ) ) {
            krsort ( $this->listeners [$eventName] );
            $this->sorted [$eventName] = call_user_func_array ( 'array_merge', $this->listeners [$eventName] );
        }
    }

    /**
     * 在当前调度器中注册一个事件监听器
     *
     * @param mixed $listener
     * @return void
     */
    public function makeListener($listener)
    {
        if ( is_string ( $listener ) ) {
            $listener = $this->createClassListener ( $listener );
        }
        return $listener;
    }

    /**
     * 创建一个基于类侦听器使用IoC容器。
     *
     * @param mixed $listener
     * @return Closure
     */
    public function createClassListener($listener)
    {
        $container = $this->container;
        return function () use($listener, $container)
        {
            $segments = explode ( '@', $listener );
            $method = count ( $segments ) == 2 ? $segments [1] : 'handle';
            $callable = array (
                    $container->make ( $segments [0] ),
                    $method
            );
            $data = func_get_args ();
            return call_user_func_array ( $callable, $data );
        };
    }

    /**
     * 从调度员删除一组听众。
     *
     * @param string $event
     * @return void
     */
    public function forget($event)
    {
        unset ( $this->listeners [$event] );
        unset ( $this->sorted [$event] );
    }
    /**
     * Determine if a given string contains a given sub-string.
     *
     * @param  string        $haystack
     * @param  string|array  $needle
     * @return bool
     */
    private function contains($haystack, $needle)
    {
        foreach ((array) $needle as $n)
        {
            if (strpos($haystack, $n) !== false) return true;
        }
        return false;
    }

    /**
     * Determine if a given string matches a given pattern.
     *
     * @param string $pattern
     * @param string $value
     * @return bool
     */
    private function is($pattern, $value)
    {
        if ( $pattern == $value ) return true;
        $pattern = preg_quote ( $pattern, '#' );
        if ( $pattern !== '/' ) {
            $pattern = str_replace ( '\*', '.*', $pattern ) . '\z';
        } else {
            $pattern = '/$';
        }
        return ( bool ) preg_match ( '#^' . $pattern . '#', $value );
    }
}